package org.jinghouyu.http.proxy.server;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

import org.jinghouyu.http.proxy.exception.HttpException;
import org.jinghouyu.http.proxy.server.callback.ResponseHandler;
import org.jinghouyu.http.proxy.stream.ChunkedNoRenderInputStream;
import org.jinghouyu.http.proxy.stream.ContentLengthInputStream;
import org.jinghouyu.http.proxy.stream.GetMethodInputStream;
import org.jinghouyu.http.proxy.utils.IOUtils;

/**
 * a channel between client and server
 * @author liujingyu
 *
 */
public class Channel {
	
	private OutputStream clientOut = null;
	private InputStream serverIn = null;
	private OutputStream serverOut = null;

	private ClientProxier clientProxier = null;
	private Request request = null;
	
	Channel(ClientProxier clientProxier, Request request, OutputStream clientOut, InputStream serverIn, OutputStream serverOut) {
		this.request = request;
		this.clientOut = clientOut;
		this.serverIn = serverIn;
		this.serverOut = serverOut;
		this.clientProxier = clientProxier;
	}

	synchronized void doChannel() throws IOException {
			doRequest(request);
			doResponse();
	}
	
	private synchronized void doRequest(Request request) throws IOException {
		InputStream bodyInput = request.getBodyInput();
		String transferEncoding = request.getHeader("Transfer-Encoding");
		InputStream bodyFinalInput = null;
		if(request.getMethod().equalsIgnoreCase("get")) {
			bodyFinalInput = new GetMethodInputStream();
		} else {
			if(transferEncoding != null) {
				if(!"chunked".equalsIgnoreCase(transferEncoding)) {
					throw new HttpException("request : Transfer-Encoding is not supported " + transferEncoding);
				}
				bodyFinalInput = new ChunkedNoRenderInputStream(bodyInput);
			} else {
				String contentLengthStr = request.getHeader("Content-Length");
				if(contentLengthStr == null) {
					throw new HttpException("request : no Transfer-Encoding or Content-Length specified");
				}
				int contentLength = Integer.parseInt(contentLengthStr);
				bodyFinalInput = new ContentLengthInputStream(contentLength, bodyInput);
			}
		}
		request.setBodyInput(bodyFinalInput);
		IOUtils.copy(request.toHttpInputStream(), serverOut);
	}
	
	private synchronized Response doResponse() throws IOException {
		Response response = Response.parse(serverIn);
		response = renderResponse(response);
		InputStream bodyInput = response.getBodyInput();
		String transferEncoding = response.getHeader("Transfer-Encoding");
		InputStream bodyFinalInput = null;
		if(transferEncoding != null) {
			if(!"chunked".equalsIgnoreCase(transferEncoding)) {
				bodyFinalInput = new ContentLengthInputStream(0, bodyInput);
			} else {
				bodyFinalInput = new ChunkedNoRenderInputStream(bodyInput);
			}
		} else {
			String contentLengthStr = response.getHeader("Content-Length");
			if(contentLengthStr == null) {
				bodyFinalInput = new ContentLengthInputStream(0, bodyInput);
			} else {
				int contentLength = Integer.parseInt(contentLengthStr);
				bodyFinalInput = new ContentLengthInputStream(contentLength, bodyInput);
			}
		}
		response.setBodyInput(bodyFinalInput);
		IOUtils.copy(response.toHttpInputStream(), clientOut);
		return response;
	}
	
	private Response renderResponse(Response response) throws IOException {
		for(ResponseHandler handler : getResponseHandlers(request, response)) {
			response = handler.handle(request, response);
		}
		return response;
	}

	private List<ResponseHandler> getResponseHandlers(Request request, Response response) {
		ProxyServer server = clientProxier.getServer();
		List<ResponseHandler> handlers = server.getResponseHandlers();
		List<ResponseHandler> result = new ArrayList<ResponseHandler>();
		for(int i = 0; handlers != null && i < handlers.size(); i++) {
			ResponseHandler handler = handlers.get(i);
			if(handler == null) continue;
			if(handler.match(request, response)) {
				result.add(handler);
			}
		}
		return result;
	}
}